#include "stdafx.h"
#include "SourceViewPage.h"
#include "SourceView.h"
#include "WatchView.h"
#include "WatchViewOneVar.h"
#include "LangSettings.h"
#include <wx/settings.h>

LUA_DEBUG_BEGIN

enum {
    MARGIN_LINENUM = 0,
    MARGIN_DEBUG = 1,
    MARGIN_FOLDING = 2,
    MARGIN_DIVIDER = 3,

    MARKNUM_BREAKPOINT = 1,
    MARKNUM_RUNNING = 2,
    MARKNUM_BACKTRACE = 3,
};

template<class Ty>
const Ty &median(const Ty &x, const Ty &min_value, const Ty &max_value) {
    return std::max<Ty>(min_value, std::min<Ty>(x, max_value));
}

BEGIN_EVENT_TABLE(SourceViewPage, wxScintilla)
EVT_KEY_DOWN                 (SourceViewPage::OnKeyDown)
EVT_LEFT_DOWN                (SourceViewPage::OnLeftDown)
EVT_MOTION                   (SourceViewPage::OnMotion)
EVT_STC_MODIFIED             (wxID_ANY, SourceViewPage::OnModified)
EVT_STC_MARGINCLICK          (wxID_ANY, SourceViewPage::OnMarginClick)
EVT_STC_CHARADDED            (wxID_ANY, SourceViewPage::OnCharAdded)
EVT_STC_HOTSPOT_CLICK        (wxID_ANY, SourceViewPage::OnHotSpotClick)
EVT_DEBUG_CHANGED_BREAKPOINTS(wxID_ANY, SourceViewPage::OnChangedBreakpoints)
END_EVENT_TABLE()

SourceViewPage::SourceViewPage(Mediator_ptr mediator,SourceView *parent)
: wxScintilla(parent, wxID_ANY)
, m_mediator(mediator)
, m_parent(parent)
, m_initialized(false)
, m_isModified(false)
, m_hasPath(false)
, m_currentLine(-1)
, m_markedLine(-1)
, m_watch(NULL)
{
    CreateGUIControls();
}

SourceViewPage::~SourceViewPage()
{

}

void SourceViewPage::CreateGUIControls()
{
    // default font for all styles
    SetViewEOL(false);
    SetEdgeMode(false ? wxSTC_EDGE_LINE : wxSTC_EDGE_NONE);
    SetViewWhiteSpace(wxSTC_WS_INVISIBLE);
    SetOvertype(false);
    SetReadOnly(false);
    SetWrapMode(wxSTC_WRAP_NONE);
    SetHotspotActiveUnderline(true);
    SetLexer(wxSTC_LEX_LUA);
    SetLayoutCache(wxSTC_CACHE_PAGE);

    // set spaces and indention
    SetTabWidth(8);
    SetUseTabs(true);
    SetTabIndents(true);
    SetBackSpaceUnIndents(true);
    SetIndent(true ? 4 : 0);
    SetIndentationGuides(false);

    SetVisiblePolicy(wxSTC_VISIBLE_STRICT | wxSTC_VISIBLE_SLOP, 1);
    SetXCaretPolicy(wxSTC_CARET_EVEN | wxSTC_VISIBLE_STRICT | wxSTC_CARET_SLOP, 1);
    SetYCaretPolicy(wxSTC_CARET_EVEN | wxSTC_VISIBLE_STRICT | wxSTC_CARET_SLOP, 1);

    // Initialize language settings.
    wxFont font = GetFont();
    //		(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
    //		false, wxT("MS Gothic"));
    int keywordNum = 0;
    for (size_t i = 0; s_stylePrefs[i].style != STYLE_END; ++i) {
        const StyleInfo &curType = s_stylePrefs[i];
        int style = curType.style;

        if (curType.foreground != NULL) {
            StyleSetForeground(style, wxColour(curType.foreground));
        }
        if (curType.background != NULL) {
            StyleSetBackground(style, wxColour(curType.background));
        }
        StyleSetFont(style, font);
        StyleSetBold(style, (curType.fontStyle & FONTSTYLE_BOLD) > 0);
        StyleSetItalic(style, (curType.fontStyle & FONTSTYLE_ITALIC) > 0);
        StyleSetUnderline(style, (curType.fontStyle & FONTSTYLE_UNDERL) > 0);
        StyleSetVisible(style, (curType.fontStyle & FONTSTYLE_HIDDEN) == 0);
        StyleSetCase(style, curType.letterCase);
        StyleSetHotSpot(style, curType.hotspot);
        if (curType.words != NULL) {
            SetKeyWords(keywordNum, curType.words);
            ++keywordNum;
        }
    }

    // Set the line number margin.
    int lineNumMargin = TextWidth(wxSTC_STYLE_LINENUMBER, wxT("_9999"));
    SetMarginType(MARGIN_LINENUM, wxSTC_MARGIN_NUMBER);
    SetMarginWidth(MARGIN_LINENUM, lineNumMargin);
    StyleSetForeground(wxSTC_STYLE_LINENUMBER, wxColour(wxT("BLACK")));
    StyleSetBackground(wxSTC_STYLE_LINENUMBER, wxColour(wxT("WHITE")));

    // Set the debug info margin.
    StyleSetForeground(wxSTC_STYLE_DEFAULT,
        wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
    SetMarginType(MARGIN_DEBUG, wxSTC_MARGIN_FORE);
    SetMarginWidth(MARGIN_DEBUG, 16);
    SetMarginSensitive(MARGIN_DEBUG, true);
    SetMarginMask(MARGIN_DEBUG,
        (1 << MARKNUM_BREAKPOINT) | (1 << MARKNUM_RUNNING) | (1 << MARKNUM_BACKTRACE));

    // Set the space margin (only the visual).
    StyleSetBackground(wxSTC_STYLE_DEFAULT, wxColour(wxT("WHITE")));
    SetMarginType(MARGIN_DIVIDER, wxSTC_MARGIN_BACK);
    SetMarginWidth(MARGIN_DIVIDER, 4);
    SetMarginSensitive(MARGIN_DIVIDER, false);
    SetMarginMask(MARGIN_DIVIDER, 0);

    // Set the folding margins.
    SetMarginType(MARGIN_FOLDING, wxSTC_MARGIN_SYMBOL);
    SetMarginMask(MARGIN_FOLDING, wxSTC_MASK_FOLDERS);
    SetFoldMarginColour(true, wxColour(_T("WHITE")));
    SetFoldMarginHiColour(true, wxColour(_T("WHITE")));
    SetMarginWidth(MARGIN_FOLDING, 12);
    SetMarginSensitive(MARGIN_FOLDING, true);
    SetProperty(wxT("fold"), wxT("1"));
    SetProperty(wxT("fold.comment"), wxT("1"));
    SetProperty(wxT("fold.compact"), wxT("1"));
    SetFoldFlags(
        wxSTC_FOLDFLAG_LINEBEFORE_CONTRACTED |
        wxSTC_FOLDFLAG_LINEAFTER_CONTRACTED);

    // Set the folding markers.
    wxColour foldColour = wxColour(wxT("DARK GREY"));
    MarkerDefine(wxSTC_MARKNUM_FOLDER, wxSTC_MARK_BOXPLUS);
    MarkerSetBackground(wxSTC_MARKNUM_FOLDER, foldColour);
    MarkerSetForeground(wxSTC_MARKNUM_FOLDER, wxColour(wxT("WHITE")));
    MarkerDefine(wxSTC_MARKNUM_FOLDEROPEN, wxSTC_MARK_BOXMINUS);
    MarkerSetBackground(wxSTC_MARKNUM_FOLDEROPEN, foldColour);
    MarkerSetForeground(wxSTC_MARKNUM_FOLDEROPEN, wxColour(wxT("WHITE")));

    wxColour lineColour = wxColour(wxT("GREEN YELLOW"));
    MarkerDefine(wxSTC_MARKNUM_FOLDERSUB, wxSTC_MARK_VLINE);
    MarkerSetBackground(wxSTC_MARKNUM_FOLDERSUB, lineColour);
    MarkerDefine(wxSTC_MARKNUM_FOLDERMIDTAIL, wxSTC_MARK_TCORNERCURVE);
    MarkerSetBackground(wxSTC_MARKNUM_FOLDERMIDTAIL, lineColour);
    MarkerDefine(wxSTC_MARKNUM_FOLDERTAIL, wxSTC_MARK_LCORNERCURVE);
    MarkerSetBackground(wxSTC_MARKNUM_FOLDERTAIL, lineColour);

    MarkerDefine(wxSTC_MARKNUM_FOLDEROPENMID, wxSTC_MARK_ARROWDOWN);
    MarkerDefine(wxSTC_MARKNUM_FOLDEREND, wxSTC_MARK_ARROW);
    MarkerSetForeground(wxSTC_MARKNUM_FOLDEROPENMID, foldColour);
    MarkerSetForeground(wxSTC_MARKNUM_FOLDEREND, foldColour);
    MarkerSetBackground(wxSTC_MARKNUM_FOLDEROPENMID, wxColour(wxT("WHITE")));
    MarkerSetBackground(wxSTC_MARKNUM_FOLDEREND, wxColour(wxT("WHITE")));

    // Set the breakpoint marker.
    MarkerDefine(MARKNUM_BREAKPOINT, wxSTC_MARK_CIRCLE);
    MarkerSetForeground(MARKNUM_BREAKPOINT, wxColour(_T("ORANGE")));
    MarkerSetBackground(MARKNUM_BREAKPOINT, wxColour(_T("RED")));

    /// Set the marker indicates current running source and line.
    MarkerDefine(MARKNUM_RUNNING, wxSTC_MARK_SHORTARROW);
    MarkerSetForeground(MARKNUM_RUNNING, wxColour(_T("RED")));
    MarkerSetBackground(MARKNUM_RUNNING, wxColour(_T("YELLOW")));

    /// Set the marker indicates backtrace source and line.
    MarkerDefine(MARKNUM_BACKTRACE, wxSTC_MARK_BACKGROUND);
    MarkerSetForeground(MARKNUM_BACKTRACE, wxColour(_T("YELLOW")));
    MarkerSetBackground(MARKNUM_BACKTRACE, wxColour(_T("GREEN")));
}

void SourceViewPage::OnMarginClick(wxScintillaEvent &event)
{
    if (event.GetMargin() == MARGIN_FOLDING) {
        int lineClick = LineFromPosition(event.GetPosition());
        int levelClick = GetFoldLevel(lineClick);
        if ((levelClick & wxSTC_FOLDLEVELHEADERFLAG) > 0) {
            ToggleFold(lineClick);
        }
    }
    else if (event.GetMargin() == MARGIN_DEBUG) {
        int lineClick = LineFromPosition(event.GetPosition());
        ToggleBreakpointFromLine(lineClick);
    }
}

void SourceViewPage::OnKeyDown(wxKeyEvent &event)
{
    event.Skip();

    if (event.ControlDown() && event.GetKeyCode() == 'S') {
        SaveSource();
    }
}

void SourceViewPage::ChangeModified(bool modified)
{
    if (!m_initialized || modified == m_isModified) {
        return;
    }

    size_t sel = m_parent->GetPageIndex(this);
    if (sel != wxNOT_FOUND) {
        if (modified) {
            // Add '*' to the source title.
            m_parent->SetPageText(sel, m_title + wxT("*"));
        }
        else {
            // Set the source title.
            m_parent->SetPageText(sel, m_title);
        }

        m_isModified = modified;
    }
}

void SourceViewPage::OnModified(wxScintillaEvent &event)
{
    if (event.GetModificationType() & (wxSTC_MOD_INSERTTEXT | wxSTC_MOD_DELETETEXT)) {
        ChangeModified(true);
    }
}

void SourceViewPage::OnCharAdded(wxScintillaEvent &event)
{
    // Change this if support for mac files with \r is needed
    if (event.GetKey() == '\n' || event.GetKey() == '\r') {
        int currentLine = GetCurrentLine();
        if (currentLine <= 0) {
            return;
        }

        // width of one indent character
        int indentWidth = (GetUseTabs() ? GetTabWidth() : 1);
        if (indentWidth == 0) {
            return;
        }

        // indent as prev line level
        int indentSize = GetLineIndentation(currentLine - 1);
        SetLineIndentation(currentLine, indentSize);

        // position = (line start pos) + (tabs count) + (space count)
        GotoPos(PositionFromLine(currentLine)
            + (indentSize / indentWidth)
            + (indentSize % indentWidth));

        // notify that the text was changed
        ChangeModified(true);
    }
}

void SourceViewPage::OnLeftDown(wxMouseEvent &event)
{
    event.Skip();
    CloseWatch();
}

void SourceViewPage::OnMotion(wxMouseEvent &event)
{
    event.Skip();
    if (m_watch != NULL && m_watch->WasInMouse()) {
        CloseWatch();
    }
}

void SourceViewPage::OnChangedBreakpoints(wxDebugEvent &/*event*/)
{
    MarkerDeleteAll(MARKNUM_BREAKPOINT);

    BreakpointList &bps = m_mediator->GetBreakpoints();
    Breakpoint bp;
    for (bp = bps.First(GetKey()); bp.IsOk(); bp = bps.Next(bp)) {
        MarkerAdd(bp.GetLine(), MARKNUM_BREAKPOINT);
    }
}

void SourceViewPage::Initialize(const Source &source)
{
    wxString str;
    for (string_array::size_type i = 0; i < source.GetLineCount(); ++i) {
        // The encoding is UTF8.
        //str += wxConvFromCtxEnc(source.GetSourceLine(i));
        str += source.GetSourceLine(i);

        // Don't insert line breaks at the last line.
        if (i < source.GetLineCount()) {
            str += _T("\n");
        }
    }

    // AddTextRaw accepts only the UTF8 string.
    AddText(str);

    SetReadOnly(source.GetPath().empty());

    // The title is converted to UTF8.
    m_key = source.GetKey();
    //m_title = wxConvFromCtxEnc(source.GetTitle());
    m_title = source.GetTitle();
    m_hasPath = (!source.GetPath().empty());
    m_currentLine = -1;
    m_initialized = true;

    wxDebugEvent event(wxEVT_DEBUG_CHANGED_BREAKPOINTS, GetId());
    OnChangedBreakpoints(event);
}

int SourceViewPage::FocusCurrentLine(int line, bool isCurrentRunning/*=true*/)
{
    wxASSERT((line < 0) || (0 < line && line <= GetLineCount()));

    // Line base is different.
    if (line > 0) {
        --line;
    }

    if (isCurrentRunning && m_currentLine >= 0) {
        MarkerDelete(m_currentLine, MARKNUM_RUNNING);
        m_currentLine = -1;
    }

    // Hide backtrace mark always.
    if (m_markedLine >= 0) {
        MarkerDeleteAll(MARKNUM_BACKTRACE);
        m_markedLine = -1;
    }

    // Set current line.
    if (line >= 0) {
        EnsureVisible(line);
        GotoLine(line);
        //int pos = PositionFromLine(line);
        //SetSelection(pos, pos);

        if (isCurrentRunning) {
            MarkerAdd(line, MARKNUM_RUNNING);
        }
        else {
            MarkerAdd(line, MARKNUM_BACKTRACE);
        }

    }

    if (isCurrentRunning) {
        m_currentLine = line;
    }
    else {
        m_markedLine = line;
    }

    CloseWatch();
    return 0;
}

void SourceViewPage::ToggleBreakpointFromLine(int line)
{
    line = median(line, 0, GetLineCount());
    m_mediator->ToggleBreakpoint(m_key, line);
}

void SourceViewPage::ToggleBreakpoint()
{
    int from, to;
    GetSelection(&from, &to);
    ToggleBreakpointFromLine(LineFromPosition(to));
}

void SourceViewPage::FocusErrorLine(int line)
{
    if (line <= 0) {
        return;
    }

    EnsureVisible(line - 1);
    SetFocus();
    SetSelection(PositionFromLine(line - 1),GetLineEndPosition(line - 1));
}

void SourceViewPage::ChangeEnable(bool enable)
{
    if (!enable) {
        FocusCurrentLine(-1, true);
    }
}

void SourceViewPage::SaveSource()
{
    if (!m_hasPath) {
        return;
    }

    string_array array;
    for (int i = 0; i < GetLineCount(); ++i) {
        wxCharBuffer cbuffer = GetLineRaw(i);
        std::string buffer = (cbuffer.data() != NULL ? cbuffer.data() : "");

        // trim newlines
        while (!buffer.empty()) {
            char c = *(--buffer.end());
            if (c == '\n' || c == '\r') {
                buffer.erase(buffer.length() - 1);
            }
            else {
                break;
            }
        }

        array.push_back(buffer);
    }

    // Erase the last line if it is newline only.
    if (!array.empty() && array.back().empty()) {
        array.pop_back();
    }

    //m_mediator->GetEngine()->SendSaveSource(m_key, array);
    ChangeModified(false);
}

void SourceViewPage::CloseWatch()
{
    if (m_watch != NULL) {
        m_watch->Close();
        m_watch = NULL;
    }
}

void SourceViewPage::OnHotSpotClick(wxScintillaEvent &event)
{
    AutoCompCancel();
    CallTipCancel();

    wxPoint clientPos = PointFromPosition(event.GetPosition());
    wxPoint screenPos = ClientToScreen(clientPos);
    int lineHeight = TextHeight(LineFromPosition(event.GetPosition()));

    CloseWatch();
    m_watch = new OneVariableWatchView(m_mediator,this, event.GetText(),
        wxPoint(screenPos.x - 50, screenPos.y + lineHeight), 
        wxSize(100, 400));
    m_watch->Show();
}

LUA_DEBUG_END
